package com.thaiopensource.xml.dtd.parse;

import java.util.Enumeration;
import java.util.Vector;

import com.thaiopensource.xml.dtd.om.AttlistDecl;
import com.thaiopensource.xml.dtd.om.AttributeDefaultDef;
import com.thaiopensource.xml.dtd.om.AttributeGroupDef;
import com.thaiopensource.xml.dtd.om.Comment;
import com.thaiopensource.xml.dtd.om.DatatypeDef;
import com.thaiopensource.xml.dtd.om.Def;
import com.thaiopensource.xml.dtd.om.ElementDecl;
import com.thaiopensource.xml.dtd.om.EnumGroupDef;
import com.thaiopensource.xml.dtd.om.ExternalEntityDecl;
import com.thaiopensource.xml.dtd.om.ExternalIdDef;
import com.thaiopensource.xml.dtd.om.ExternalIdRef;
import com.thaiopensource.xml.dtd.om.Flag;
import com.thaiopensource.xml.dtd.om.FlagDef;
import com.thaiopensource.xml.dtd.om.IgnoredSection;
import com.thaiopensource.xml.dtd.om.IncludedSection;
import com.thaiopensource.xml.dtd.om.InternalEntityDecl;
import com.thaiopensource.xml.dtd.om.ModelGroupDef;
import com.thaiopensource.xml.dtd.om.NameSpec;
import com.thaiopensource.xml.dtd.om.NameSpecDef;
import com.thaiopensource.xml.dtd.om.NotationDecl;
import com.thaiopensource.xml.dtd.om.OverriddenDef;
import com.thaiopensource.xml.dtd.om.ParamDef;
import com.thaiopensource.xml.dtd.om.ProcessingInstruction;
import com.thaiopensource.xml.dtd.om.TopLevel;

class Decl
{
  static final int REFERENCE = 0; // entity
  static final int REFERENCE_END = 1;
  static final int ELEMENT = 2; // params
  static final int ATTLIST = 3; // params
  static final int ENTITY = 4; // params
  static final int NOTATION = 5; // params
  static final int INCLUDE_SECTION = 6; // params + decls
  static final int IGNORE_SECTION = 7; // params + value
  static final int COMMENT = 8; // value
  static final int PROCESSING_INSTRUCTION = 9; // value

  Decl (final int type)
  {
    this.type = type;
  }

  final int type;
  Vector params;
  String value;
  Entity entity;
  Vector decls;

  @Override
  public boolean equals (final Object obj)
  {
    if (obj == null || !(obj instanceof Decl))
      return false;
    final Decl other = (Decl) obj;
    if (this.type != other.type)
      return false;
    if (this.entity != other.entity)
      return false;
    if (this.value != null && !this.value.equals (other.value))
      return false;
    if (this.params != null)
    {
      final int n = this.params.size ();
      if (other.params.size () != n)
        return false;
      for (int i = 0; i < n; i++)
        if (!this.params.elementAt (i).equals (other.params.elementAt (i)))
          return false;
    }
    return true;
  }

  ElementDecl createElementDecl ()
  {
    final ParamStream ps = new ParamStream (params, true);
    final NameSpec nameSpec = Param.paramsToNameSpec (ps);
    return new ElementDecl (nameSpec, Param.paramsToModelGroup (ps));
  }

  AttlistDecl createAttlistDecl ()
  {
    final ParamStream ps = new ParamStream (params, true);
    final NameSpec nameSpec = Param.paramsToNameSpec (ps);
    return new AttlistDecl (nameSpec, Param.paramsToAttributeGroup (ps));
  }

  TopLevel createEntityDecl (final DtdBuilder db)
  {
    final ParamStream ps = new ParamStream (params);
    ps.advance ();
    if (ps.type != Param.PERCENT)
      return createGeneralEntityDecl (db, ps.value);
    ps.advance ();
    final String name = ps.value;
    final Entity entity = db.lookupParamEntity (name);
    if (entity.decl == null)
    {
      entity.decl = this;
      return createDef (entity);
    }
    else
    {
      Entity overridden = entity.overrides;
      while (overridden.decl != null)
        overridden = overridden.overrides;
      overridden.decl = this;
      return new OverriddenDef (createDef (overridden), entity.entityValue != null &&
                                                        entity.entityValue.equals (overridden.entityValue));
    }
  }

  static Def createDef (final Entity entity)
  {
    final String name = entity.name;
    switch (entity.semantic)
    {
      case Entity.SEMANTIC_MODEL_GROUP:
        entity.modelGroup = entity.toModelGroup ();
        return new ModelGroupDef (name, entity.modelGroup);
      case Entity.SEMANTIC_ATTRIBUTE_GROUP:
        entity.attributeGroup = Param.paramsToAttributeGroup (entity.parsed);
        return new AttributeGroupDef (name, entity.attributeGroup);
      case Entity.SEMANTIC_DATATYPE:
        entity.datatype = Param.paramsToDatatype (entity.parsed);
        return new DatatypeDef (name, entity.datatype);
      case Entity.SEMANTIC_ENUM_GROUP:
        entity.enumGroup = entity.toEnumGroup ();
        return new EnumGroupDef (name, entity.enumGroup);
      case Entity.SEMANTIC_FLAG:
        entity.flag = Param.paramsToFlag (entity.parsed);
        return new FlagDef (name, entity.flag);
      case Entity.SEMANTIC_NAME_SPEC:
        entity.nameSpec = entity.toNameSpec ();
        return new NameSpecDef (name, entity.nameSpec);
      case Entity.SEMANTIC_ATTRIBUTE_DEFAULT:
        entity.attributeDefault = Param.paramsToAttributeDefault (entity.parsed);
        return new AttributeDefaultDef (name, entity.attributeDefault);
    }
    if (entity.problem == Entity.NO_PROBLEM && !entity.overridden)
      throw new RuntimeException ("unexplained problem for entity " + entity.name);
    if (entity.text == null)
      return new ExternalIdDef (name, entity.getExternalId ());
    return new ParamDef (name, entity.entityValue);
  }

  TopLevel createGeneralEntityDecl (final DtdBuilder db, final String name)
  {
    Entity entity = db.lookupGeneralEntity (name);
    while (entity.decl != null)
      entity = entity.overrides;
    entity.decl = this;
    if (entity.text == null)
      return new ExternalEntityDecl (name, entity.getExternalId ());
    else
      return new InternalEntityDecl (name, new String (entity.text));
  }

  IncludedSection createIncludedSection (final DtdBuilder db)
  {
    final Flag flag = Param.paramsToFlag (params);
    final Vector contents = declsToTopLevel (db, decls.elements ());
    final TopLevel [] tem = new TopLevel [contents.size ()];
    for (int i = 0; i < tem.length; i++)
      tem[i] = (TopLevel) contents.elementAt (i);
    return new IncludedSection (flag, tem);
  }

  static Vector declsToTopLevel (final DtdBuilder db, final Enumeration decls)
  {
    final Vector v = new Vector ();
    int level = 0;
    while (decls.hasMoreElements ())
    {
      TopLevel t = null;
      final Decl decl = (Decl) decls.nextElement ();
      switch (decl.type)
      {
        case COMMENT:
          t = new Comment (decl.value);
          break;
        case PROCESSING_INSTRUCTION:
          t = decl.createProcessingInstruction ();
          break;
        case NOTATION:
          t = decl.createNotationDecl (db);
          break;
        case ELEMENT:
          t = decl.createElementDecl ();
          break;
        case ATTLIST:
          t = decl.createAttlistDecl ();
          break;
        case ENTITY:
          t = decl.createEntityDecl (db);
          break;
        case INCLUDE_SECTION:
          t = decl.createIncludedSection (db);
          break;
        case IGNORE_SECTION:
          t = decl.createIgnoredSection ();
          break;
        case REFERENCE:
          if (decl.entity.text == null)
            t = decl.createExternalIdRef (db, decls);
          else
            level++;
          break;
        case REFERENCE_END:
          if (level == 0)
            return v;
          --level;
          break;
      }
      if (t != null)
        v.addElement (t);
    }
    return v;
  }

  ExternalIdRef createExternalIdRef (final DtdBuilder db, final Enumeration decls)
  {
    final Vector v = declsToTopLevel (db, decls);
    final TopLevel [] tem = new TopLevel [v.size ()];
    for (int i = 0; i < tem.length; i++)
      tem[i] = (TopLevel) v.elementAt (i);
    return new ExternalIdRef (entity.name, entity.getExternalId (), entity.uri, entity.encoding, tem);
  }

  IgnoredSection createIgnoredSection ()
  {
    return new IgnoredSection (Param.paramsToFlag (params), value);
  }

  ProcessingInstruction createProcessingInstruction ()
  {
    final int len = value.length ();
    int i;
    for (i = 0; i < len && !isWS (value.charAt (i)); i++)
    {}
    final String target = value.substring (0, i);
    if (i < len)
    {
      for (++i; i < len && isWS (value.charAt (i)); i++)
      {}
    }
    return new ProcessingInstruction (target, value.substring (i, len));
  }

  static private boolean isWS (final char c)
  {
    switch (c)
    {
      case '\n':
      case '\r':
      case '\t':
      case ' ':
        return true;
    }
    return false;
  }

  NotationDecl createNotationDecl (final DtdBuilder db)
  {
    final ParamStream ps = new ParamStream (params);
    ps.advance ();
    return new NotationDecl (ps.value, db.lookupNotation (ps.value).getExternalId ());
  }

  static void examineElementNames (final DtdBuilder db, final Enumeration decls)
  {
    while (decls.hasMoreElements ())
    {
      final Decl decl = (Decl) decls.nextElement ();
      switch (decl.type)
      {
        case ELEMENT:
        case ATTLIST:
          Param.examineElementNames (db, decl.params.elements ());
          break;
        case INCLUDE_SECTION:
          examineElementNames (db, decl.decls.elements ());
          break;
      }
    }
  }
}
